<?php
/*
 * This file is part of phpep.
 * 
 * phpep
 * 
 * Copyright (C) 2007 Andrés Otárola Alvarado pgugged/cast -- otarola.andres@gmail.com
 * 
 * phpep is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * 
 * phpep is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *    
 */
require_once APP_DIR . 'db/MySqlDb.class.php';
require_once APP_DIR . 'db/OracleDb.class.php';
 
////Clase abstracta basica para todos los modelos
abstract class LiveRecord{
	protected $has_many = array();
	protected $belongs_to = array();
	protected $has_one = array();
	protected $has_many_through = array();
	//nombre de la tabla que hace referencia el modelo
	protected $table_name;
	//existe o no esta referencia en la tabla
	private $exist;
	//registro de errores de validaciones
	private $errors = array();
	//mensajes de errores por defecto
	private $__error_messages = array(
		'not_empty' => 'Debe ingresar una entrada',
		'unique' => 'Entrada ingresada existente',
		'is_alpha' => 'La entrada no debe contener numeros',
		'has_records' => 'El registro tiene datos asociados'
	);
	//mensajes de errores personalizados
	protected $error_messages = array();
	//validaciones incluidas
	private $__validate_procedures = array(
		'not_empty','unique','is_alpha','is_numeric'
	);
	//validaciones seleccionadas
	protected $validations = array();
	//validaciones personalizadas
	protected $validate_procedures = array();
	//nombre de columnas de la tabla
	private $table_fields = array();
	//atributos del modelo
	private $attributes = array();
	//conexion unica para todos los modelos
	static private $db;	
	
    function __construct($attrs = null) {
    	//Por defecto el objeto no existe en la base de datos
    	$this->exist = false;
    	if(!isset(self::$db)){
    		self::$db = new MySqlDb();
    		//self::$db = new OracleDb();
    	}
    	$this->set_table_fields();
    	if(isset($attrs))
    		$this->set_attributes($attrs);
    }
    
    public function get_errors(){
    	$error_messages = array_merge($this->__error_messages, $this->error_messages);
    	$tmp = array();
    	foreach($this->errors as $error_type => $attributes){
    		foreach($attributes as $attr){
    			$tmp[] = array($attr => $error_messages[$error_type]);
    			$r = $error_messages[$error_type];
    		}
    	}
    	return $tmp;
    }    
    
    private function unique($args){
    	if($this->exist){
    		$s = $this->find_all("WHERE id = '{$this->id}'");
    		$s = $s[0];
    	}
    	$tmp = array();
    	foreach($args as $attr){
    		$sql = "SELECT {$attr} FROM {$this->table_name} WHERE {$attr} = '{$this->attributes[$attr]}'";
    		$res = self::$db->query($sql);
    		if(self::$db->num_rows($res)){
    			if($this->exist){
    				if(strtolower($s->attributes[$attr]) == strtolower($this->attributes[$attr]));
    				else
    				array_push($tmp, $attr);
    			}
    			else
    				array_push($tmp, $attr);
    		}
    	}
    	if(sizeof($tmp) > 0)
    		$this->errors['unique'] = $tmp;
    }
    
    private function is_alpha($args){
    	$tmp = array();
    	foreach($args as $attr){
    		$fail = false;
    		$value = $this->attributes[$attr];
    		for($i=0;$i<strlen($value);$i++)
    			if(is_numeric($value[$i])){
    				$fail = true;
    			}
    		if($fail == true)
    			array_push($tmp,$attr);
    	}
    	if(sizeof($tmp) > 0)
    		$this->errors['is_alpha'] = $tmp;
    }
    
    private function is_numeric($args){
    	$tmp = array();
    	foreach($args as $attr){
    		if(!is_numeric($this->attributes[$attr])){
    			array_push($tmp,$attr);
    		}
    	}
    	if(sizeof($tmp) > 0)
    		$this->errors['is_numeric'] = $tmp;    	
    }
    
    private function not_empty($args){
    	$tmp = array();
    	foreach($args as $attr){
    		if(empty($this->attributes[$attr])){
    			array_push($tmp,$attr);
    		}
    	}
    	if(sizeof($tmp) > 0)
    		$this->errors['not_empty'] = $tmp;
    }
    private function has_records($args){
    	$tmp = array();
    	foreach($args as $attr){
	    	if(sizeof($this->$attr) > 0){
	    		//$this->pato("estoy aca!");
	    			array_push($tmp, $attr);
	    	}
    	}
    	if(sizeof($tmp) > 0)
    		$this->errors['has_records'] = $tmp;    	
    }
    public function delete(){
    	$this->do_validations();
    	if(sizeof($this->errors) > 0) return false;
    	$pk_conditions = $this->get_attributes_primary_key();
    	$sql = "DELETE FROM {$this->table_name} WHERE {$pk_conditions}";
    	$this->db->query($sql);
    	return true;
    }
           
    private function do_validations(){
    	$validations = array_merge($this->__validate_procedures, $this->validate_procedures);
    	foreach($this->validations as $function => $arguments){
    		$this->$function($arguments);
    	}
    }    

    private function get_attributes_primary_key(){
    	$pks = self::$db->get_primary_keys($this->table_name);
    	$new_values = $this->attributes;
    	foreach($pks as $pk){
    		$tmp[] = "{$pk} = '{$new_values[$pk]}'";
    	}
    	$conditions = implode(' AND ',$tmp);
    	return $conditions;
    }    
 
    private function set_table_fields(){
    	$fields = self::$db->get_table_fields($this->table_name);
    	foreach($fields as $field){
    		$this->table_fields[$field] = $field;
    		//$this->pato($field);
    		$this->attributes[$field] = '';
    	}
    }
    
    public function set_attributes($attrs){
    	if(is_array($attrs)){
    		foreach($attrs as $k => $v){
    			if(in_array($k,array_keys($this->table_fields))){
    				$this->attributes[$k] = $v;
    			}
    			//TODO: fixme =)
    			/*else{
    				throw new Exception("no existe el atributo {$k}");
    			}*/
    				
    		}

    	}
    }
    
    private function get_belongs_to_assoc($assoc){
	    $assoc_info = $this->belongs_to[$assoc];
    	$model = $assoc_info['class_name'];
    	$obj = new $model();
    	$primary_key = $assoc_info['references_to'];
    	$foreign_key = $assoc_info['foreign_key'];
    	$conditions = "WHERE {$primary_key} = {$this->$foreign_key}";
    	$all_assoc = $obj->find_all($conditions);
    	$single = $all_assoc[0]; 
    	return $single;
    }
    
    public function save_join_assoc($assoc,$object){
    	$assoc_info = $this->has_many_through[$assoc];
    	$join_table = $assoc_info['join_table'];
    	$fk_own = $assoc_info['foreign_key_own'];
    	$pk_own = $assoc_info['references_to_own'];
    	$pk_other = $assoc_info['references_to_other'];
    	$fk_other = $assoc_info['foreign_key_other'];
    	$sql = "INSERT INTO {$join_table}({$fk_own}, {$fk_other}) VALUES({$this->$pk_own},{$object->$pk_other})";
    	self::$db->query($sql);
    }
    
    private function get_has_many_through($assoc){
    	$assoc_info = $this->has_many_through[$assoc];
    	$model = $assoc_info['class_name'];
    	$join_table = $assoc_info['join_table'];
    	$obj = new $model();
    	$primary_key = $assoc_info['references_to_own'];
    	$foreign_key = $assoc_info['foreign_key_own'];
    	$foreign_key_other = $assoc_info['foreign_key_other'];
    	$columns = $obj->get_attributes_columns();
    	$other_table = $obj->table_name;
    	$sql =	"SELECT DISTINCT {$columns} FROM {$join_table}, {$other_table} ".
				"WHERE ({$join_table}.{$foreign_key} = {$this->$primary_key} ".
				"AND {$obj->table_name}.{$primary_key} = {$join_table}.{$foreign_key_other})";
    	//$this->pato($sql);
    	$res = self::$db->query($sql);
    	$all_assoc = array();
    	while($row = self::$db->fetch_array($res) ){
    		$obj = new $model($row);
    		$obj->set_existence(true);
    		$all_assoc[] = $obj;
    	}
    	return $all_assoc;
    }
    
    private function get_has_many_assoc($assoc){
    	if(!$this->exist) return null;
    	$assoc_info = $this->has_many[$assoc];
    	$model = $assoc_info['class_name'];
    	$obj = new $model();
    	$primary_key = $assoc_info['references_to'];
    	$conditions = "WHERE {$assoc_info['foreign_key']} = {$this->$primary_key}";
    	$all_assoc = $obj->find_all($conditions);
    	return $all_assoc;
    }
    //TODO: mucha repeticion de codigo en relaciones sql. hacer algo...
    private function get_has_one_assoc($assoc){
    	$assoc_info = $this->has_one[$assoc];
    	$model = $assoc_info['class_name'];
    	$obj = new $model();
    	$primary_key = $assoc_info['references_to'];
    	$conditions = "WHERE {$assoc_info['foreign_key']} = {$this->$primary_key}";
    	$all_assoc = $obj->find_all($conditions);
    	$single = $all_assoc[0];
    	return $single;
    }
    public function pato($arg=null){
    	echo "CUAC!: {$arg}<br />";
    }
    public function __get($member) {
        if (isset($this->attributes[$member])) {
            return $this->attributes[$member];
        }
        else if($member == 'db'){
        	return self::$db;
        }
        if(in_array($member,array_keys($this->has_many))){
        	$children = $this->get_has_many_assoc($member);
        	return $children;
        }
        if(in_array($member,array_keys($this->belongs_to))){
        	$parent = $this->get_belongs_to_assoc($member);
        	return $parent;
        }
        if(in_array($member,array_keys($this->has_one))){
        	$child = $this->get_has_one_assoc($member);
        	return $child;
        }
        if(in_array($member,array_keys($this->has_many_through))){
        	$children = $this->get_has_many_through($member);
        	return $children;
        }
        throw new Exception("no existe el atributo {$member}");
    }

    public function __set($member, $value) {
        if (isset($this->attributes[$member])) {
            $this->attributes[$member] = $value;
            return;            
        }
        else if($member == 'db'){
        	self::$db = $value;
        }        
        throw new Exception("no existe el atributo {$member}");
    }
         
    private function add_error($param,$type){
    	$this->errors[$param] = $type;
    }
    
    //// Los metodos setExistence y getExistence modifican la variable exist
    //// la cual indica si el objeto creado existe o no en la base de datos 
    private final function set_existence($bool){
    	$this->exist = $bool;
    }
    private final function get_existence(){
    	return $this->exist;
    }
    
    private function get_attributes_update_sql(){
    	$tmp = array();
    	foreach($this->attributes as $k => $v){
    		$tmp[] = "$k = '$v'";
    	}
    	$new_values = implode(',',$tmp);
    	return $new_values;
    }
   
    private function get_attributes_insert_sql(){
    	$tmp = array();
    	foreach ($this->attributes as $k => $v){
    		if($k == 'id' and !empty($v)){
    			$tmp[] = "'{$v}'";
    		}
    		else if($k == 'id' and empty($v)){
    			
    		}
    		else{
    			$tmp[] = "'{$v}'";
    		}
    	}
    	
    	$new_values = implode(',',$tmp);
    	return $new_values;
    }
    private function get_attributes_columns(){
    	$tmp = array();
    	foreach ($this->attributes as $k => $v){
    		$tmp[] = "{$this->table_name}.{$k}";
    	}
    	$columns = implode(',',$tmp);
    	return $columns;    	
    }
    
    private function get_attributes_insert_columns(){
    	$tmp = array();
    	foreach ($this->attributes as $k => $v){
    		
    		if($k == 'id' and !empty($this->attributes[$k])){
    			$tmp[] = "{$this->table_name}.{$k}";
    		}
    		else if($k == 'id' and empty($v)){
    			
    		}
    		else{
    			$tmp[] = "{$this->table_name}.{$k}";
    		}
    	}
    	
    	$columns = implode(',',$tmp);
    	return $columns;    	
    }
    public function save(){
    	$this->do_validations();
    	if(sizeof($this->errors) > 0) return false;
    	if($this->get_existence()){
    		$pk_conditions = $this->get_attributes_primary_key();
    		$new_values = $this->get_attributes_update_sql();
    		$sql = "UPDATE {$this->table_name} SET $new_values WHERE $pk_conditions";
    	}
    	else{
    		$new_values = 	$this->get_attributes_insert_sql();
    		$columns 	=	$this->get_attributes_insert_columns();
    		$sql = "INSERT INTO {$this->table_name}({$columns}) VALUES ($new_values)";
    	}
    	try{;
    		//echo $sql.'<br />';
    		$this->db->query($sql);
    		if(!$this->exist && $this->has_attr('id')) $this->update_pk();
    	}catch(Exception $e){
    		echo "<br />ocurrio un error inesperado : " . ($e->getMessage());
    		exit(0);
    	}
    	return true;
    }
    private function has_attr($attr){
    	return in_array($attr,array_keys($this->attributes));
    }    
    private function update_pk(){
    	$sql = "SELECT id FROM {$this->table_name} ORDER BY id DESC";
    	$res = self::$db->query($sql);
    	$row = self::$db->fetch_array($res);
    	$this->id = $row['id'];
    }
    
    public function find_all($conditions = null){
    	$columns = implode(',',array_keys($this->table_fields));
		$sql = "SELECT {$columns} FROM {$this->table_name} {$conditions}"; 
    	$model = get_class($this);
    	$res = self::$db->query($sql);
    	$tmp = array();
    	while($row = self::$db->fetch_array($res) ){
    		$obj = new $model($row);
    		$obj->set_existence(true);
    		$tmp[] = $obj;
    	}
    	return $tmp;
    }
    
    private function tiene_assoc($assoc){
    	return (count($this->$assoc) > 0);
    }
    
    public function __call($method, $arguments) {
        $prefix_find = strtolower(substr($method, 0, 8));
        $property_find = strtolower(substr($method, 8));
        $prefix_has = strtolower(substr($method, 0, 6));
        $property_has = strtolower(substr($method, 6));        
        if (empty($prefix_find) || empty($property_find)) {
            return;
        }
        if ($prefix_find == "find_by_" && in_array($property_find,array_keys($this->table_fields))) {
        	$this->find_by_($property_find,$arguments[0]);
        }
        else if($prefix_has == "tiene_"){
        	 $has_many = array_keys($this->has_many);
        	 $has_many_through = array_keys($this->has_many_through);
        	 $belongs_to = array_keys($this->belongs_to);
        	 $has_one = array_keys($this->has_one);
        	 $total_array = array_merge($has_many,$has_many_through,$belongs_to,$has_one);
        	 if(in_array($property_has,$total_array))
        	 	return $this->tiene_assoc($property_has);
        }
        else{
        	throw new Exception("No existe el metodo {$method}");
        }
        
    }
    
    private function find_by_($property,$argument){
    	//TODO: Terminar de implementar los metodos dinámicos
    } 
    
    protected function call_stored_procedure($sp){
    	self::$db->call_stored_procedure($sp);
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    function __destruct() {}
            
}
?>
